#include <stdio.h>
#include <string.h>
#include <windows.h>

#define STR_LENGTH 20
#define IS_VARIABLE 0x01
#define IS_NUMBER   0x02

#define STACK_MAX 10

#define OP_ADD  0x01
#define OP_LET  0x02
#define OP_DISP 0x04
#define OP_FOR  0x08
#define OP_ROF  0x10

#pragma pack( 2 ) 

unsigned int loop_index = 0 ;
char loop_opr_stack[STACK_MAX][STR_LENGTH] ;
char loop_var_stack[STACK_MAX][STR_LENGTH] ;
unsigned int loop_opr_type_stack[STACK_MAX] ;
int loop_addr_stack[STACK_MAX] ;

// all strings need to be from 0x30 to 0x39.
int str_to_int( char str[] ) {

  int c, num = 0 ;
  unsigned int i = 0 ;

  while( c = str[i] ) {
    num *= 10 ;
    num += c - 0x30 ;
    i++ ;
  }

  return num ;

}

// return          0  : error
//        IS_VARIABLE : is variable (a-z)
//        IS_NUMBER   : is number
// i think there is a room to do refactoring.
unsigned int judge_type( char *str ) {

  int c ;
  unsigned int i = 0, type = 0 ;

  while( c = str[i] ) {

    if( type == IS_VARIABLE && i > 0 )
      return 0 ;

    if( c >= 0x30 && c <= 0x39 ) {
      type = IS_NUMBER ;
    } else if( c >= 0x61 && c <= 0x7a ) {
      type = IS_VARIABLE ;
      if( i > 0 )
        return 0 ;
    } else {
      return 0 ;
    }
    i++ ;

  }

  return type ;

}

// parse 1line operation and valid it
// return 0 : not valid
//        1 : valid
int parse( char str[], char op[], char opr1[], char opr2[],
           unsigned int *op_type, unsigned int *opr1_type,
           unsigned int *opr2_type, unsigned int line_num ) {

  unsigned int i = 0, j = 0 ;
  int c ;
  char *p ;

  op[0]   = 0 ;
  opr1[0] = 0 ;
  opr2[0] = 0 ;
  *op_type = 0 ;

  // there is a room to improve.
  p = op ;
  while( c = str[i] ) {
    if( c == 0x20 ) {         // ' '
      *p = 0 ;
      if( j == 0 )
        p = opr1 ;
      else if( j == 1 )
        p = opr2 ;
      j++ ;
    } else if( c == 0xa ) {   // \n

    } else {
      *p = c ;
      p++ ;
    }
    i++ ;
  }
  *p = 0 ;

  *opr1_type = judge_type( opr1 ) ;
  *opr2_type = judge_type( opr2 ) ;

  if( strcmp( op, "add" ) == 0 ) {
    *op_type = OP_ADD ;
  } else if( strcmp( op, "let" ) == 0 ) {
    *op_type = OP_LET ;
  } else if( strcmp( op, "disp" ) == 0 ) {
    *op_type = OP_DISP ;
  } else if( strcmp( op, "for" ) == 0 ) {
    *op_type = OP_FOR ;
  } else if( strcmp( op, "rof" ) == 0 ) {
    *op_type = OP_ROF ;
  } else {
    printf( "can't understand op \"%s\" at %d\n", op, line_num ) ;
    return 0 ;
  }

  if( ( *op_type & OP_ROF ) != 0 ) {
    if( j != 0 ) {
      printf( "%s requires no operand at %d\n", op, line_num ) ;
      return 0 ;
    } 
  } else {
    if( ( *opr1_type & IS_VARIABLE ) == 0 ) {
      printf( "can't use opr1 \"%s\" for %s at %d\n", opr1, op, line_num ) ;
      return 0 ;
    }
    if( ( *op_type & ( OP_ADD | OP_LET | OP_FOR ) ) != 0 ) {
      if( ( *opr2_type & ( IS_NUMBER | IS_VARIABLE ) ) == 0 ) {
        printf( "can't use opr2 \"%s\" for %s at %d\n", opr2, op, line_num ) ;
        return 0 ;
      }
      if( j != 2 ) {
        printf( "%s requires 2 operands at %d\n", op, line_num ) ;
        return 0 ;
      } 
    } else {
      if( j != 1 ) {
        printf( "%s requires 1 operand at %d\n", op, line_num ) ;
        return 0 ;
      } 
    }

  }

  return 1 ;

}

// c needs to be from 0x61 to 0x7a
unsigned int calc_addr( int c ) {
  return 0x00402000 + ( c - 0x61 ) * 4 ;
}

void put_int( FILE *fp, int num ) {
  putc(   num & 0x000000ff, fp ) ;
  putc( ( num & 0x0000ff00 ) >> 8, fp ) ;
  putc( ( num & 0x00ff0000 ) >> 16, fp ) ;
  putc( ( num & 0xff000000 ) >> 24, fp ) ;
}

int main( int argc, char **argv ) {

  FILE *fp, *out ;
  unsigned int i ;
  char str[ STR_LENGTH ], op[ STR_LENGTH ], opr1[ STR_LENGTH ], opr2[ STR_LENGTH ] ;
  int loop_tmp ;
  unsigned int op_type, opr1_type, opr2_type ;
  unsigned int line_num ;
  IMAGE_DOS_HEADER image_dos_header ;
  IMAGE_NT_HEADERS32 image_nt_headers32 ;
  IMAGE_SECTION_HEADER image_section_headers[3] ;
  IMAGE_IMPORT_DESCRIPTOR image_import_descriptors[1] ;

  if( argc != 3 ) {
    printf( "usage : %s <input code file> <output binary file>\n", argv[ 0 ] ) ;
    return -1 ;
  }

  if( ! ( fp = fopen( argv[ 1 ], "r" ) ) ) {
    printf( "can't open input file %s\n", argv[ 1 ] ) ;
    return -1 ;
  }

  if( ! ( out = fopen( argv[ 2 ], "wb" ) ) ) {
    printf( "can't open output file %s\n", argv[ 2 ] ) ;
    fclose( fp ) ;
    return -1 ;
  }

  // output image_dos_header
  image_dos_header.e_magic = 0x5a4d ;
  image_dos_header.e_cblp  = 0x90 ;
  image_dos_header.e_cp    = 0x03 ;
  image_dos_header.e_crlc  = 0x00 ;
  image_dos_header.e_cparhdr = 0x04 ;
  image_dos_header.e_minalloc = 0x0000 ;
  image_dos_header.e_maxalloc = 0xffff ;
  image_dos_header.e_ss = 0x00 ;
  image_dos_header.e_sp = 0xb8 ;
  image_dos_header.e_csum = 0x00 ;
  image_dos_header.e_ip = 0x00 ;
  image_dos_header.e_cs = 0x00 ;
  image_dos_header.e_lfarlc = 0x40 ;
  image_dos_header.e_ovno = 0x00 ;
  image_dos_header.e_res[0] = 0x00 ;
  image_dos_header.e_res[1] = 0x00 ;
  image_dos_header.e_res[2] = 0x00 ;
  image_dos_header.e_res[3] = 0x00 ;
  image_dos_header.e_oemid = 0x00 ;
  image_dos_header.e_oeminfo = 0x00 ;
  image_dos_header.e_res2[0] = 0x00 ;
  image_dos_header.e_res2[1] = 0x00 ;
  image_dos_header.e_res2[2] = 0x00 ;
  image_dos_header.e_res2[3] = 0x00 ;
  image_dos_header.e_res2[4] = 0x00 ;
  image_dos_header.e_res2[5] = 0x00 ;
  image_dos_header.e_res2[6] = 0x00 ;
  image_dos_header.e_res2[7] = 0x00 ;
  image_dos_header.e_res2[8] = 0x00 ;
  image_dos_header.e_res2[9] = 0x00 ;
  image_dos_header.e_lfanew = 0x00000080 ;

  fwrite( &image_dos_header, sizeof( image_dos_header ), 1, out ) ;

  // output dos stab
  putc( 0xb8, out ) ;
  putc( 0x01, out ) ;
  putc( 0x4c, out ) ;
  putc( 0xcd, out ) ;
  putc( 0x21, out ) ;
  for( i = 0x45; i < 0x80; i++ ) {
    putc( 0x00, out ) ;
  }

  // output image_nt_headers32
  image_nt_headers32.Signature = 0x00004550 ;
  image_nt_headers32.FileHeader.Machine = 0x014c ;
  image_nt_headers32.FileHeader.NumberOfSections = 0x0003 ;
  image_nt_headers32.FileHeader.TimeDateStamp = 0x4da65f98 ; // temporary
  image_nt_headers32.FileHeader.PointerToSymbolTable = 0x00000000 ; 
  image_nt_headers32.FileHeader.NumberOfSymbols = 0x00000000 ; 
  image_nt_headers32.FileHeader.SizeOfOptionalHeader = 0x00e0 ; 
  image_nt_headers32.FileHeader.Characteristics = 0x0307 ; 
  image_nt_headers32.OptionalHeader.Magic = 0x010b ;
  image_nt_headers32.OptionalHeader.MajorLinkerVersion = 0x02 ;
  image_nt_headers32.OptionalHeader.MinorLinkerVersion = 0x15 ;
  image_nt_headers32.OptionalHeader.SizeOfCode = 0x00000200 ;
  image_nt_headers32.OptionalHeader.SizeOfInitializedData = 0x00000200 ;
  image_nt_headers32.OptionalHeader.SizeOfUninitializedData = 0x00000000 ;
  image_nt_headers32.OptionalHeader.AddressOfEntryPoint = 0x00001000 ;
  image_nt_headers32.OptionalHeader.BaseOfCode = 0x00001000 ;
  image_nt_headers32.OptionalHeader.BaseOfData = 0x00002000 ;
  image_nt_headers32.OptionalHeader.ImageBase = 0x00400000 ;
  image_nt_headers32.OptionalHeader.SectionAlignment = 0x00001000 ;
  image_nt_headers32.OptionalHeader.FileAlignment = 0x00000200 ;
  image_nt_headers32.OptionalHeader.MajorOperatingSystemVersion = 0x0004 ;
  image_nt_headers32.OptionalHeader.MinorOperatingSystemVersion = 0x0000 ;
  image_nt_headers32.OptionalHeader.MajorImageVersion = 0x0001 ;
  image_nt_headers32.OptionalHeader.MinorImageVersion = 0x0000 ;
  image_nt_headers32.OptionalHeader.MajorSubsystemVersion = 0x0004 ;
  image_nt_headers32.OptionalHeader.MinorSubsystemVersion = 0x0000 ;
  image_nt_headers32.OptionalHeader.Win32VersionValue = 0x00000000 ;
  image_nt_headers32.OptionalHeader.SizeOfImage = 0x00004000 ;
  image_nt_headers32.OptionalHeader.SizeOfHeaders = 0x00000200 ;
  image_nt_headers32.OptionalHeader.CheckSum = 0x00000000 ;
  image_nt_headers32.OptionalHeader.Subsystem = 0x0003 ;
  image_nt_headers32.OptionalHeader.DllCharacteristics = 0x8540 ;
  image_nt_headers32.OptionalHeader.SizeOfStackReserve = 0x00100000 ;
  image_nt_headers32.OptionalHeader.SizeOfStackCommit = 0x00001000 ;
  image_nt_headers32.OptionalHeader.SizeOfHeapReserve = 0x00100000 ;
  image_nt_headers32.OptionalHeader.SizeOfHeapCommit = 0x00001000 ;
  image_nt_headers32.OptionalHeader.LoaderFlags = 0x00000000 ;
  image_nt_headers32.OptionalHeader.NumberOfRvaAndSizes = 0x00000010 ;
  for( i = 0; i < 16; i++ ) {
    image_nt_headers32.OptionalHeader.DataDirectory[i].VirtualAddress = 0x00000000 ;
    image_nt_headers32.OptionalHeader.DataDirectory[i].Size = 0x00000000 ;
  }
  // for .idata (not yet)
  image_nt_headers32.OptionalHeader.DataDirectory[1].VirtualAddress = 0x00003000 ;
  image_nt_headers32.OptionalHeader.DataDirectory[1].Size = 0x00000060 ;
  fwrite( &image_nt_headers32, sizeof( image_nt_headers32 ), 1, out ) ;

  // output image_section_header(.text)
  image_section_headers[0].Name[0] = 0x2e ;
  image_section_headers[0].Name[1] = 0x74 ;
  image_section_headers[0].Name[2] = 0x65 ;
  image_section_headers[0].Name[3] = 0x78 ;
  image_section_headers[0].Name[4] = 0x74 ;
  image_section_headers[0].Name[5] = 0x00 ;
  image_section_headers[0].Name[6] = 0x00 ;
  image_section_headers[0].Name[7] = 0x00 ;
  image_section_headers[0].Misc.VirtualSize = 0x0010 ;
  image_section_headers[0].VirtualAddress = 0x1000 ;
  image_section_headers[0].SizeOfRawData = 0x0200 ;
  image_section_headers[0].PointerToRawData = 0x0200 ;
  image_section_headers[0].PointerToRelocations = 0x0000 ;
  image_section_headers[0].PointerToLinenumbers = 0x0000 ;
  image_section_headers[0].NumberOfRelocations = 0x0000 ;
  image_section_headers[0].NumberOfLinenumbers = 0x0000 ;
  image_section_headers[0].Characteristics = 0x60300020 ;
  fwrite( &image_section_headers[0], sizeof( image_section_headers[0] ), 1, out ) ;

  // output image_section_header(.data)
  image_section_headers[1].Name[0] = 0x2e ;
  image_section_headers[1].Name[1] = 0x64 ;
  image_section_headers[1].Name[2] = 0x61 ;
  image_section_headers[1].Name[3] = 0x74 ;
  image_section_headers[1].Name[4] = 0x61 ;
  image_section_headers[1].Name[5] = 0x00 ;
  image_section_headers[1].Name[6] = 0x00 ;
  image_section_headers[1].Name[7] = 0x00 ;
  image_section_headers[1].Misc.VirtualSize = 0x0068 ; // integer * 26
  image_section_headers[1].VirtualAddress = 0x2000 ;
  image_section_headers[1].SizeOfRawData = 0x0200 ;
  image_section_headers[1].PointerToRawData = 0x0400 ;
  image_section_headers[1].PointerToRelocations = 0x0000 ;
  image_section_headers[1].PointerToLinenumbers = 0x0000 ;
  image_section_headers[1].NumberOfRelocations = 0x0000 ;
  image_section_headers[1].NumberOfLinenumbers = 0x0000 ;
  image_section_headers[1].Characteristics = 0xC0300040 ;
  fwrite( &image_section_headers[1], sizeof( image_section_headers[1] ), 1, out ) ;

  // output image_section_header(.idata)
  image_section_headers[2].Name[0] = 0x2e ;
  image_section_headers[2].Name[1] = 0x69 ;
  image_section_headers[2].Name[2] = 0x64 ;
  image_section_headers[2].Name[3] = 0x61 ;
  image_section_headers[2].Name[4] = 0x74 ;
  image_section_headers[2].Name[5] = 0x61 ;
  image_section_headers[2].Name[6] = 0x00 ;
  image_section_headers[2].Name[7] = 0x00 ;
  image_section_headers[2].Misc.VirtualSize = 0x0060 ; // ?
  image_section_headers[2].VirtualAddress = 0x3000 ;
  image_section_headers[2].SizeOfRawData = 0x0200 ;
  image_section_headers[2].PointerToRawData = 0x0600 ;
  image_section_headers[2].PointerToRelocations = 0x0000 ;
  image_section_headers[2].PointerToLinenumbers = 0x0000 ;
  image_section_headers[2].NumberOfRelocations = 0x0000 ;
  image_section_headers[2].NumberOfLinenumbers = 0x0000 ;
  image_section_headers[2].Characteristics = 0xC0300040 ;
  fwrite( &image_section_headers[2], sizeof( image_section_headers[2] ), 1, out ) ;

  // output padding
  while( ftell( out ) < 0x200 ) {
    putc( 0x00, out ) ;
  }

  // output .text
  line_num = 0 ;
  while( fgets( str, 1024, fp ) != NULL ) {

    line_num++ ;

    if( ! parse( str, op, opr1, opr2, &op_type, &opr1_type,
                 &opr2_type, line_num ) ) {
      fclose( fp ) ;
      fclose( out ) ;
      return -1 ;
    }

    if( op_type == OP_ADD ) {

      putc( 0xa1, out ) ;
      put_int( out, calc_addr( opr1[0] ) ) ;

      if( opr2_type == IS_NUMBER ) {

        putc( 0x05, out ) ;
        put_int( out, str_to_int( opr2 ) ) ;

      } else {

        putc( 0x8b, out ) ;
        putc( 0x15, out ) ;
        put_int( out, calc_addr( opr1[0] ) ) ;

        putc( 0xa1, out ) ;
        put_int( out, calc_addr( opr2[0] ) ) ;

        putc( 0x8d, out ) ;
        putc( 0x04, out ) ;
        putc( 0x02, out ) ;

      }

      putc( 0xa3, out ) ;
      put_int( out, calc_addr( opr1[0] ) ) ;

    } else if( op_type == OP_LET ) {

      if( opr2_type == IS_NUMBER ) {

        putc( 0xb8, out ) ;
        put_int( out, str_to_int( opr2 ) ) ;

      } else {

        putc( 0xa1, out ) ;
        put_int( out, calc_addr( opr2[0] ) ) ;

      }

      putc( 0xa3, out ) ;
      put_int( out, calc_addr( opr1[0] ) ) ;

    } else if( op_type == OP_DISP ) {

      putc( 0xa1, out ) ;
      put_int( out, calc_addr( opr1[0] ) ) ;

      putc( 0x50, out ) ;

      putc( 0xa1, out ) ;
      put_int( out, 0x00403030 ) ;

      putc( 0xff, out ) ;
      putc( 0xd0, out ) ;

      putc( 0x58, out ) ;

    } else if( op_type == OP_FOR ) {

      strcpy( loop_var_stack[loop_index], opr1 ) ;
      strcpy( loop_opr_stack[loop_index], opr2 ) ;
      loop_opr_type_stack[loop_index] = opr2_type ;

      putc( 0xb8, out ) ;
      put_int( out, 0x00000000 ) ;

      putc( 0xa3, out ) ;
      put_int( out, calc_addr( opr1[0] ) ) ;

      putc( 0xe9, out ) ;
      loop_addr_stack[loop_index] = ftell( out ) ;
      put_int( out, 0x00000000 ) ; // temporary

      if( loop_index < STACK_MAX )  // unless error
        loop_index++ ;

    } else if( op_type == OP_ROF ) {

      if( loop_index > 0 )  // unless error
        loop_index-- ;

      putc( 0xa1, out ) ;
      put_int( out, calc_addr( loop_var_stack[loop_index][0] ) ) ;

      putc( 0x40, out ) ;

      putc( 0xa3, out ) ;
      put_int( out, calc_addr( loop_var_stack[loop_index][0] ) ) ;

      loop_tmp = ftell( out ) ;

      // need to check fseek success or not
      fseek( out, loop_addr_stack[loop_index], SEEK_SET ) ;
      put_int( out, loop_tmp - loop_addr_stack[loop_index] - 4 ) ;
      fseek( out, loop_tmp, SEEK_SET ) ;

      if( loop_opr_type_stack[loop_index] == IS_NUMBER ) {

        putc( 0xa1, out ) ;
        put_int( out, calc_addr( loop_var_stack[loop_index][0] ) ) ;

        putc( 0x3d, out ) ;
        put_int( out, str_to_int( loop_opr_stack[loop_index] ) - 1 ) ;

        putc( 0x0f, out ) ;
        putc( 0x8e, out ) ;
        put_int( out, - ( ftell( out ) - loop_addr_stack[loop_index] ) ) ;

      } else {

        putc( 0x8b, out ) ;
        putc( 0x15, out ) ;
        put_int( out, calc_addr( loop_var_stack[loop_index][0] ) ) ;

        putc( 0xa1, out ) ;
        put_int( out, calc_addr( loop_opr_stack[loop_index][0] ) ) ;

        putc( 0x39, out ) ;
        putc( 0xc2, out ) ;

        putc( 0x0f, out ) ;
        putc( 0x8c, out ) ;
        put_int( out, - ( ftell( out ) - loop_addr_stack[loop_index] ) ) ;

      }

    }

  }

  putc( 0xC3, out ) ;

  // too fool logic!!
  // calculating the size of .text
  image_section_headers[0].Misc.VirtualSize = ftell( out ) - 0x200 ;

  while( ftell( out ) < 0x400 ) {
    putc( 0x00, out ) ;
  }

  // output .data
  putc( 0x00, out ) ;
  putc( 0x00, out ) ;
  putc( 0x00, out ) ;
  putc( 0x00, out ) ;
  while( ftell( out ) < 0x600 ) {
    putc( 0x00, out ) ;
  }

  // output .idata
  image_import_descriptors[0].OriginalFirstThunk = 0x3028 ;
  image_import_descriptors[0].TimeDateStamp = 0x0000 ;
  image_import_descriptors[0].ForwarderChain = 0x0000 ;
  image_import_descriptors[0].Name = 0x3048 ;
  image_import_descriptors[0].FirstThunk = 0x3030 ;
  fwrite( &image_import_descriptors[0], sizeof( image_import_descriptors[0] ), 1, out ) ;

  while( ftell( out ) < 0x628 ) {
    putc( 0x00, out ) ;
  }

  put_int( out, 0x00003038 ) ;

  while( ftell( out ) < 0x630 ) {
    putc( 0x00, out ) ;
  }

  put_int( out, 0x00003038 ) ;

  while( ftell( out ) < 0x638 ) {
    putc( 0x00, out ) ;
  }

  // puts "prtnum"
  putc( 0x00, out ) ;
  putc( 0x00, out ) ;
  putc( 0x70, out ) ;
  putc( 0x72, out ) ;
  putc( 0x74, out ) ;
  putc( 0x6e, out ) ;
  putc( 0x75, out ) ;
  putc( 0x6d, out ) ;
  putc( 0x00, out ) ;

  while( ftell( out ) < 0x648 ) {
    putc( 0x00, out ) ;
  }

  // puts "prtnum.dll"
  putc( 0x70, out ) ;
  putc( 0x72, out ) ;
  putc( 0x74, out ) ;
  putc( 0x6e, out ) ;
  putc( 0x75, out ) ;
  putc( 0x6d, out ) ;
  putc( 0x2e, out ) ;
  putc( 0x64, out ) ;
  putc( 0x6c, out ) ;
  putc( 0x6c, out ) ;

  while( ftell( out ) < 0x800 ) {
    putc( 0x00, out ) ;
  }
 
  // too fool logic!!
  // output image_section_header of .text again
  if( fseek( out, 0x178, SEEK_SET ) ) {
    printf( "failed to seek to image_section_header." ) ;
    return -1 ;
  }
  fwrite( &image_section_headers[0], sizeof( image_section_headers[0] ), 1, out ) ;

  fclose( out ) ;
  fclose( fp ) ;

  return 0 ;
}

